/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief UDP connection implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#include "connectionUDP.h"
#include "owndebug.h"
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>


//**************************************************************************************
//**************************************************************************************
//**************************************************************************************
//**************************************************************************************
//**************************************************************************************

int connectionUDPInit(TConnectionUDP *con)
{
  int r;

  con->hostEntry = NULL;
  con->socket_fd = 0;
  
  // Initialize mutex. Don't want concurrent access over 'Connection'
  r = pthread_mutex_init(&con->mutex, NULL);
  assert(r == 0);

  return CONNECTIONUDP_OK;
}
//**************************************************************************************

int connectionUDPDeinit(TConnectionUDP *con)
{
  // Free resources
  if (con->socket_fd) close(con->socket_fd);

  // Destroy the mutex
  pthread_mutex_destroy(&con->mutex);
  
  return CONNECTIONUDP_OK;
}
//**************************************************************************************

int connectionUDPCreateListeningSocket(TConnectionUDP *con, int port)
{
  int result = CONNECTIONUDP_ERROR;
 
  // LOCK
  pthread_mutex_lock(&con->mutex);

  // Create Socket for listening
  if ((con->socket_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
    dPrintG(1, "Failed to CREATE a UDP listening socket!"); 
    
  } else {
  
    // Zero the address structure (required for maximum portability)
    memset((char *) &con->socket_addr, 0, sizeof(struct sockaddr_in));
    
    // sets few struct values
    con->socket_addr.sin_family = AF_INET; // Internet protocol
    con->socket_addr.sin_port = htons(port); // Store port
    con->socket_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Any address
    
    // Binds a name to a socket
    if (bind(con->socket_fd,
	     (struct sockaddr *)&con->socket_addr,
	     sizeof(struct sockaddr_in)) < 0) {
      dPrintG(1, "Failed to BIND a socket!");
      
    } else {
      result = CONNECTIONUDP_OK;
    
    } // bind
  } // if(con->socket_fd...)
  
  // UNLOCK
  pthread_mutex_unlock(&con->mutex);

  return result;
}
//**************************************************************************************

int connectionUDPCreateSendingSocket(TConnectionUDP *con, 
				     const char *host, int port)
{
  int result = CONNECTIONUDP_ERROR;
  assert( host != NULL && port != -1);
  assert(con->socket_fd == 0); // MUST NOT BE PREVIOUSLY INITIALIZED!

  // LOCK
  pthread_mutex_lock(&con->mutex);

  // Create socket.
  if ((con->socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
    dPrintG(1, "Unable to create UDP Client socket!");

  } else {

    //Zero socket addr and set required parameters
    memset((char *) &con->socket_addr, 0, sizeof(struct sockaddr_in));
    con->socket_addr.sin_family = AF_INET; // Set to INET type
    con->socket_addr.sin_port = htons(port); // Converts the port value to netform
    
    // Do hostname lookup. Also ip addresses are valid.
    if ((con->hostEntry = gethostbyname(host)) == NULL) {
      dPrintG(1, "Invalid hostname! (%s)", hstrerror(h_errno));
      close(con->socket_fd); // clean up

    } else {
      
      // Assign address if it was valid
      con->socket_addr.sin_addr = *((struct in_addr *)con->hostEntry->h_addr);

      // Finally, change result to OK
      result = CONNECTIONUDP_OK;
      
    } // if(con->hostEntry...)
  } // if(con->socket_fd...)

  // UNLOCK
  pthread_mutex_unlock(&con->mutex);

  // Init OK!
  return CONNECTIONUDP_OK;
}

//**************************************************************************************

int connectionUDPSendDataFormatted(TConnectionUDP *con, const char *format, ...)
{
  char buffer[2048];
  int bytes;
  va_list va; //parameter list
   
  va_start(va, format); //get list
  bytes = vsnprintf(buffer, sizeof(buffer), format, va); //print list
  va_end(va); //end list

  assert(bytes < sizeof(buffer));


  dPrintG(10, "Sent:  \"%s\"", buffer);
  
  // LOCK
  pthread_mutex_lock(&con->mutex);

  // Send the string
  bytes = connectionUDPSendData(con, (unsigned char *)buffer, bytes);

  // UNLOCK
  pthread_mutex_unlock(&con->mutex);
  
  return bytes;
}
//**************************************************************************************

int connectionUDPSendData(TConnectionUDP *con, const unsigned char *buffer, int bufLen)
{
  int result = CONNECTIONUDP_ERROR;
  assert(buffer != NULL && bufLen > 0);

  if (bufLen > UDPFRAME_MAX_SIZE) {
    dPrintG(2, "WARNING: Attempted to Send oversized UDP frame! Discarding!");
    return CONNECTIONUDP_ERROR;

  } else if (bufLen > UDPFRAME_SAFE_SIZE) {
    dPrintG(4, "WARNING: The UDP frame to-be-sent exceeds the UDP 'Safe' size!");

  }

  // LOCK
  pthread_mutex_lock(&con->mutex);

  if (sendto(con->socket_fd, buffer, bufLen, 0, (struct sockaddr *)&con->socket_addr, 
	     sizeof(struct sockaddr_in)) == -1) {
    dPrintG(2,"WARNING: Error sending data through UDP socket!");

  } else {
    result = CONNECTIONUDP_OK;
  }

  // UNLOCK 
  pthread_mutex_unlock(&con->mutex);
  

  return result;
}
//**************************************************************************************

int connectionUDPReceiveDataTimed(TConnectionUDP *con, 
			       unsigned char *buffer, 
			       int bufLen,
			       int timeout_ms)
{
  // Parameters
  struct pollfd p;
  struct sockaddr_in sender;
  socklen_t slen = sizeof(sender);
  int v;
  
  // Check
  assert(con->socket_fd > 0 && bufLen >= 1);

  // Assign poll() parameters
  p.fd = con->socket_fd; // File descriptor
  p.events = POLLIN; // Only interested in poll_in
  p.revents = 0; // To satisfy valgrind

  // Call poll()
  v = poll(&p, 1, timeout_ms);
  
  if (v == 0) {
    dPrintG(15,"Poll returned Zero - Timeout! No data to read.");
  
  } else if (v == 1 && (p.revents & POLLIN)) {
    dPrintG(15, "Poll returned Events!");

    // LOCK
    pthread_mutex_lock(&con->mutex);
    
    v = recvfrom(con->socket_fd, buffer, bufLen, 0, 
		 (struct sockaddr *)&sender, &slen);
    // UNLOCK
    pthread_mutex_unlock(&con->mutex);
    if (v < 0) { 
      dPrintG(2, "Error reading data from UDP Socket!");   
      v = CONNECTIONUDP_ERROR;
    }  
    
  } else {
    dPrintG(2,"Poll returned error! (%s)!", strerror(errno));
    v = 0;
  }
  
  return v;
}
//**************************************************************************************

int connectionUDPReceiveData(TConnectionUDP *con, unsigned char *buffer, int bufLen)
{
  int v;
  struct sockaddr_in sender;
  socklen_t slen = sizeof(sender);
  assert(con->socket_fd > 0 && bufLen >= 1);

  // LOCK
  pthread_mutex_lock(&con->mutex);

  v = recvfrom(con->socket_fd, buffer, bufLen, 0, 
	       (struct sockaddr *)&sender, &slen);

  // UNLOCK
  pthread_mutex_unlock(&con->mutex);

  if (v < 0) { 
    dPrintG(2, "Error reading data from UDP Socket!");   
    v = CONNECTIONUDP_ERROR;
  }  
  
  return v;
}
//**************************************************************************************
//**************************************************************************************
//**************************************************************************************
//**************************************************************************************

